package com.alibaba.japan.club.runtime.springmvc.servlet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.OrderComparator;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.ModelAndViewDefiningException;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.WebUtils;

import com.alibaba.japan.club.runtime.springmvc.pipeline.JapanAroundPipeline;
import com.alibaba.japan.club.runtime.springmvc.pipeline.JapanEntrancePipeline;
import com.alibaba.japan.club.runtime.springmvc.pipeline.JapanExceptionPipeline;
import com.alibaba.japan.club.runtime.springmvc.servlet.context.ResponseContextHolder;

/**
 * 该类主要是对Spring MVC的调用请求的处理流程进行了部分调整<br>
 * Spring MVC原生的DispatcherServlet类在处理页面级别的错误时,不进行错误处理并显示错误页面,而是直接将错误完全抛出<br>
 * JapanDispatcherServlet类调整之后,可以对Spring MVC页面级的错误也进行捕获得进行错误页面统一处理
 * 
 * @author:shaochuan.wangsc
 * @date:2009-3-24
 * 
 */
public class JapanDispatcherServlet extends DispatcherServlet {
	/**
	 * 
	 */
	private static final long serialVersionUID = 2580745573756269717L;

	/**
	 * Well-known name for the aroundPipeline object in the bean factory for this namespace. Only used when
	 * "detectAllAroundPipelines" is turned off.
	 */
	public static final String AROUND_PIPELINE_BEAN_NAME = "aroundPipeline";

	/**
	 * Well-known name for the entrancePipeline object in the bean factory for this namespace. Only used when
	 * "detectAllEntrancePipelines" is turned off.
	 */
	public static final String ENTRANCE_PIPELINE_BEAN_NAME = "entrancePipeline";

	/**
	 * Well-known name for the exceptionPipeline object in the bean factory for this namespace. Only used when
	 * "detectAllExceptionPipelines" is turned off.
	 */
	public static final String EXCEPTION_PIPELINE_BEAN_NAME = "exceptionPipeline";

	/** Detect all detectAllAroundPipelines or just expect "aroundPipeline" bean? */
	private boolean detectAllAroundPipelines = true;

	/** Detect all detectAllEntrancePipelines or just expect "entrancePipeline" bean? */
	private boolean detectAllEntrancePipelines = true;

	/** Detect all detectAllExceptionPipelines or just expect "exceptionPipeline" bean? */
	private boolean detectAllExceptionPipelines = true;

	/** List of entrancePipelines used by this servlet */
	@SuppressWarnings("unchecked")
	private List aroundPipelines;

	/** List of entrancePipelines used by this servlet */
	@SuppressWarnings("unchecked")
	private List entrancePipelines;

	/** List of exceptionPipelines used by this servlet */
	@SuppressWarnings("unchecked")
	private List exceptionPipelines;

	/** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
	private boolean threadContextInheritable = false;

	/**
	 * Set whether to detect all entrancePipeline beans in this servlet's context. Else, just a single bean with name
	 * "entrancePipeline" will be expected.
	 * <p>
	 * Default is "true". Turn this off if you want this servlet to use a single entrancePipeline, despite multiple
	 * entrancePipeline beans being defined in the context.
	 */
	public void setDetectAllEntrancePipelines(boolean detectAllEntrancePipelines) {
		this.detectAllEntrancePipelines = detectAllEntrancePipelines;
	}

	/**
	 * Set whether to expose the LocaleContext and RequestAttributes as inheritable for child threads (using an
	 * {@link java.lang.InheritableThreadLocal}).
	 * <p>
	 * Default is "false", to avoid side effects on spawned background threads. Switch this to "true" to enable
	 * inheritance for custom child threads which are spawned during request processing and only used for this request
	 * (that is, ending after their initial task, without reuse of the thread).
	 * <p>
	 * <b>WARNING:</b> Do not use inheritance for child threads if you are accessing a thread pool which is configured
	 * to potentially add new threads on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}), since this
	 * will expose the inherited context to such a pooled thread.
	 */
	public void setThreadContextInheritable(boolean threadContextInheritable) {
		this.threadContextInheritable = threadContextInheritable;
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>
	 * May be overridden in subclasses in order to initialize further strategy objects.
	 */
	public void initStrategies(ApplicationContext context) {
		initAroundPipelines(context);
		initEntrancePipelines(context);
		super.initStrategies(context);
		initExceptionPipelines(context);
	}

	/**
	 * Initialize the aroundPipelines used by this class.
	 */
	@SuppressWarnings("unchecked")
	private void initAroundPipelines(ApplicationContext context) {
		this.aroundPipelines = null;

		if (this.detectAllAroundPipelines) {
			// Find all aroundPipelines in the ApplicationContext,
			// including ancestor contexts.
			Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, JapanAroundPipeline.class,
					true, false);
			if (!matchingBeans.isEmpty()) {
				this.aroundPipelines = new ArrayList(matchingBeans.values());
				// We keep aroundPipelines in sorted order.
				Collections.sort(this.aroundPipelines, new OrderComparator());
			}
		} else {
			try {
				Object hm = context.getBean(AROUND_PIPELINE_BEAN_NAME, JapanAroundPipeline.class);
				this.aroundPipelines = Collections.singletonList(hm);
			} catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we do not need a aroundPipelines.
			}
		}
	}

	/**
	 * Initialize the entrancePipelines used by this class.
	 */
	@SuppressWarnings("unchecked")
	private void initEntrancePipelines(ApplicationContext context) {
		this.entrancePipelines = null;

		if (this.detectAllEntrancePipelines) {
			// Find all entrancePipelines in the ApplicationContext,
			// including ancestor contexts.
			Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, JapanEntrancePipeline.class,
					true, false);
			if (!matchingBeans.isEmpty()) {
				this.entrancePipelines = new ArrayList(matchingBeans.values());
				// We keep entrancePipelines in sorted order.
				Collections.sort(this.entrancePipelines, new OrderComparator());
			}
		} else {
			try {
				Object hm = context.getBean(ENTRANCE_PIPELINE_BEAN_NAME, JapanEntrancePipeline.class);
				this.entrancePipelines = Collections.singletonList(hm);
			} catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we do not need a entrancePipelines.
			}
		}
	}

	/**
	 * Initialize the exceptionPipelines used by this class.
	 */
	@SuppressWarnings("unchecked")
	private void initExceptionPipelines(ApplicationContext context) {
		this.exceptionPipelines = null;

		if (this.detectAllExceptionPipelines) {
			// Find all exceptionPipelines in the ApplicationContext,
			// including ancestor contexts.
			Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, JapanExceptionPipeline.class,
					true, false);
			if (!matchingBeans.isEmpty()) {
				this.exceptionPipelines = new ArrayList(matchingBeans.values());
				// We keep exceptionPipelines in sorted order.
				Collections.sort(this.exceptionPipelines, new OrderComparator());
			}
		} else {
			try {
				Object hm = context.getBean(EXCEPTION_PIPELINE_BEAN_NAME, JapanExceptionPipeline.class);
				this.exceptionPipelines = Collections.singletonList(hm);
			} catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we do not need a exceptionPipelines.
			}
		}
	}

	/**
	 * Process the actual dispatching to the handler.
	 * <p>
	 * The handler will be obtained by applying the servlet's HandlerMappings in order. The HandlerAdapter will be
	 * obtained by querying the servlet's installed HandlerAdapters to find the first that supports the handler class.
	 * <p>
	 * All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers themselves to decide which
	 * methods are acceptable.
	 * 
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 */
	@Override
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isTraceEnabled()) {
			logger.trace("Bound request context to thread: " + request);
		}
		// execute dispatchInternal
		if (this.aroundPipelines != null) {
			executeAroundPipelines(request, response, 0);
		} else {
			doDispatchInternal(request, response);
		}
	}

	/**
	 * 执行aroundPipelines
	 * 
	 * @author:shaochuan.wangsc
	 * @date:2009-4-18
	 * @param aroundPipelines
	 * @param index
	 * @throws Exception
	 */
	private void executeAroundPipelines(HttpServletRequest request, HttpServletResponse response, int index)
			throws Exception {
		//
		if (aroundPipelines.size() > index) {
			JapanAroundPipeline aroundPipeline = (JapanAroundPipeline) aroundPipelines.get(index);
			try {
				aroundPipeline.preHandleAroundPipeline(request, response);
				// execute aroundPipelines
				if ((aroundPipelines.size() - 1) == index) {
					doDispatchInternal(request, response);
				}else{
					executeAroundPipelines(request, response, ++index);
				}
			} finally {
				aroundPipeline.postHandleAroundPipeline(request, response);
			}

		}
	}

	/**
	 * 执行内部dispatch
	 * 
	 * @author:shaochuan.wangsc
	 * @date:2009-4-18
	 */
	protected void doDispatchInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Expose current LocaleResolver and request as LocaleContext.
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);

		// Expose current RequestAttributes to current thread.
		RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);

		// Expose current response to current thread.
		HttpServletResponse previousResponse = ResponseContextHolder.getResponse();
		ResponseContextHolder.setResponse(response, this.threadContextInheritable);

		Exception exception = null;
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		int interceptorIndex = -1;

		try {
			ModelAndView mv = null;
			boolean errorView = false;

			try {
				// execute entrancePipelines
				if (this.entrancePipelines != null) {
					for (Object object : entrancePipelines) {
						JapanEntrancePipeline entrancePipeline = (JapanEntrancePipeline) object;
						if (!entrancePipeline.isAuthorized(processedRequest, response)) {
							ModelAndView modelAndView = entrancePipeline.handleEntranceInternal(processedRequest,
									response);
							render(modelAndView, processedRequest, response);
							return;
						}
					}
				}

				processedRequest = checkMultipart(request);
				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					// Do we need view name translation?
					mv = new ModelAndView();
					mv.setViewName(getDefaultViewName(request));
					render(mv, processedRequest, response);
					return;
				}

				// Apply preHandle methods of registered interceptors.
				HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
				if (interceptors != null) {
					for (int i = 0; i < interceptors.length; i++) {
						HandlerInterceptor interceptor = interceptors[i];
						if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
							triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
							return;
						}
						interceptorIndex = i;
					}
				}

				// Actually invoke the handler.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// Do we need view name translation?
				if (mv != null && !mv.hasView()) {
					mv.setViewName(getDefaultViewName(request));
				}

				// Apply postHandle methods of registered interceptors.
				if (interceptors != null) {
					for (int i = interceptors.length - 1; i >= 0; i--) {
						HandlerInterceptor interceptor = interceptors[i];
						interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
					}
				}

				// render template
				render(mv, processedRequest, response);
				// Trigger after-completion for successful outcome.
				triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
				return;
			} catch (ModelAndViewDefiningException ex) {
				// triggerAfterException
				triggerAfterException(processedRequest, response, ex);

				logger.error("ModelAndViewDefiningException encountered", ex);
				mv = ex.getModelAndView();
				exception = ex;
			} catch (Exception ex) {
				// triggerAfterException
				triggerAfterException(processedRequest, response, ex);

				logger.error("Exception encountered", ex);
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(processedRequest, response, handler, ex);
				errorView = (mv != null);
				exception = ex;
			}

			// Did the handler return a view to render?
			if (mv != null && !mv.wasCleared()) {
				render(mv, processedRequest, response);
				if (errorView) {
					WebUtils.clearErrorRequestAttributes(request);
				}
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName()
							+ "': assuming HandlerAdapter completed request handling");
				}
				throw exception;
			}

			// Trigger after-completion for successful outcome.
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
		} catch (Exception ex) {
			// triggerAfterException
			triggerAfterException(processedRequest, response, ex);
			// Trigger after-completion for thrown exception.
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
			throw ex;
		} catch (Error err) {
			// triggerAfterException
			triggerAfterException(processedRequest, response, err);

			ServletException ex = new NestedServletException("Handler processing failed", err);
			// Trigger after-completion for thrown exception.
			logger.error("Error encountered", ex);
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
			throw ex;
		} finally {
			// Clean up any resources used by a multipart request.
			if (processedRequest != request) {
				cleanupMultipart(processedRequest);
			}

			// Reset thread-bound context.
			RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
			LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);

			// Reset thread-bound response context.
			ResponseContextHolder.setResponse(previousResponse, this.threadContextInheritable);

			// Clear request attributes.
			requestAttributes.requestCompleted();
			if (logger.isTraceEnabled()) {
				logger.trace("Cleared thread-bound request context: " + request);
			}
		}
	}

	/**
	 * 如果发生异常,则先执行此方法
	 * 
	 * @author:shaochuan.wangsc
	 * @date:2009-4-18
	 * @param throwable
	 */
	private void triggerAfterException(HttpServletRequest request, HttpServletResponse response, Throwable throwable)
			throws Exception {
		// execute exceptionPipelines
		if (this.exceptionPipelines != null) {
			for (Object object : exceptionPipelines) {
				JapanExceptionPipeline exceptionPipeline = (JapanExceptionPipeline) object;
				exceptionPipeline.handleExceptionInternal(request, response, throwable);
			}
		}
	}

	/**
	 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors. Will just invoke afterCompletion for all
	 * interceptors whose preHandle invocation has successfully completed and returned true.
	 * 
	 * @param mappedHandler the mapped HandlerExecutionChain
	 * @param interceptorIndex index of last interceptor that successfully completed
	 * @param ex Exception thrown on handler execution, or <code>null</code> if none
	 * @see HandlerInterceptor#afterCompletion
	 */
	private void triggerAfterCompletion(HandlerExecutionChain mappedHandler, int interceptorIndex,
			HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {

		// Apply afterCompletion methods of registered interceptors.
		if (mappedHandler != null) {
			HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
			if (interceptors != null) {
				for (int i = interceptorIndex; i >= 0; i--) {
					HandlerInterceptor interceptor = interceptors[i];
					try {
						interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
					} catch (Throwable ex2) {
						logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
					}
				}
			}
		}
	}
}
